#include <errno.h>
#include <sys/mman.h>
#include "MemBlocks.h"
#include "MemChunks.h"

// 4M: 4×1024×1024
#define CHUNK_SIZE 4194304

CMemChunks::CMemChunks()
{
    m_lstChunk.SetMemOptor(&CMemChunks::ChunkNodeAllocator, &CMemChunks::ChunkNodeDeleter, this);
    m_lstUnfixed.SetMemOptor(&CMemChunks::UnfixedNodeAllocator, &CMemChunks::UnfixedNodeDeleter, this);
}

CMemChunks::~CMemChunks()
{
    Destroy();
}

ChunkNode *CMemChunks::AddNewChunk()
{
    bool bRet = false;
    ChunkNode *pNode = m_lstChunk.NewNode(CHUNK_SIZE - sizeof(ChunkNode));

    if (nullptr == pNode)
        return nullptr;

    pNode->nLength = CHUNK_SIZE;
    pNode->pEnd = (unsigned char *)pNode + CHUNK_SIZE;
    pNode->pStart = (unsigned char *)pNode + sizeof(ChunkNode);
    pNode->pCurPos = pNode->pStart;
    bRet = m_lstChunk.PushBack(pNode);

    if (!bRet)
    {
        m_lstChunk.DeleteNode(pNode);
        return nullptr;
    }

    return pNode;
}

void *CMemChunks::Alloc(unsigned int nLength, BlockType bt)
{
    void *ptr = nullptr;

    if (SMALL_BLOCK == bt)
    {
        ChunkNode *pNode = m_lstChunk.GetTailer();

        if (nullptr == pNode)
        {
            pNode = AddNewChunk();

            if (nullptr == pNode)
                return nullptr;
        }

        unsigned int nUnuse = (unsigned char *)pNode->pEnd - (unsigned char *)pNode->pCurPos;

        if (nUnuse >= nLength)
        {
            ptr = pNode->pCurPos;
            pNode->pCurPos = (unsigned char *)pNode->pCurPos + nLength;
        }
        else
        {
            pNode = AddNewChunk();

            if (nullptr == pNode)
                return nullptr;

            ptr = pNode->pCurPos;
            pNode->pCurPos = (unsigned char *)pNode->pCurPos + nLength;
        }
    }
    else
    {
        UnfixedNode *pNode = AddUnfixedNode(nLength);

        if (nullptr == pNode)
            return nullptr;

        ptr = (unsigned char *)pNode + sizeof(UnfixedNode);
    }

    return ptr;
}

bool CMemChunks::Free(void *ptr, BlockType bt)
{
    bool bRet = false;

    if (nullptr == ptr)
        return false;

    if (LARGE_BLOCK == bt)
    {
        UnfixedNode *pNode = (UnfixedNode *)((unsigned char *)ptr - sizeof(UnfixedNode));
        bRet = DelUnfixedNode(pNode);
    }

    return bRet;
}

void CMemChunks::Destroy()
{
    m_lstChunk.Destroy();
    m_lstUnfixed.Destroy();
}

void *CMemChunks::ChunkNodeAllocator(int iLen)
{
    void *ptr = mmap(0, iLen, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);

    if (MAP_FAILED == ptr)
    {
        printf("Error: mmap\tfile=%s\tline=%d\n", __FILE__, __LINE__);
        ptr = nullptr;
    }

    return ptr;
}

void CMemChunks::ChunkNodeDeleter(ChunkNode *pNode)
{
    if (nullptr != pNode)
    {
        int iRet = munmap(pNode, pNode->nLength);

        if (0 != iRet)
            printf("Error: munmap error:%d\tfile=%s\tline=%d\n", errno, __FILE__, __LINE__);
    }
}

UnfixedNode *CMemChunks::AddUnfixedNode(unsigned int iLen)
{
    bool bRet = false;
    UnfixedNode *pNode = m_lstUnfixed.NewNode(iLen);

    if (nullptr == pNode)
        return nullptr;

    pNode->nLength = iLen + sizeof(UnfixedNode);
    bRet = m_lstUnfixed.PushBack(pNode);

    if (!bRet)
    {
        m_lstUnfixed.DeleteNode(pNode);
        return nullptr;
    }

    return pNode;
}

bool CMemChunks::DelUnfixedNode(UnfixedNode *pNode)
{
    bool bRet = false;

    if (nullptr == pNode)
    {
        printf("parameter error.\n");
        return false;
    }

    bRet = m_lstUnfixed.Remove(pNode);

    if (!bRet)
    {
        printf("Remove error.\n");
        return false;
    }

    m_lstUnfixed.DeleteNode(pNode);

    return true;
}

void *CMemChunks::UnfixedNodeAllocator(int iLen)
{
    void *ptr = mmap(0, iLen, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);

    if (MAP_FAILED == ptr)
    {
        printf("Error: mmap\tfile=%s\tline=%d\n", __FILE__, __LINE__);
        return nullptr;
    }

    return ptr;
}

void CMemChunks::UnfixedNodeDeleter(UnfixedNode *pNode)
{
    if (nullptr != pNode)
    {
        int iRet = munmap(pNode, pNode->nLength);

        if (0 != iRet)
            printf("Error: munmap\tfile=%s\tline=%d\n", __FILE__, __LINE__);
    }
}